package org.tamal.java;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

/**
 * LDAP utility methods.
 * @author Tamal Kanti Nath
 */
public final class LdapUtil {

    private static final String CN = "cn";

    private LdapUtil() {
        // Utility class
    }

    /**
     * Returns LDAP search result.
     * @param user the user name (e.g. Tamal_Nath@amer.dell.com)
     * @param pass the password
     * @param base the search base (can be null)
     * @param filter the filter e.g. "(&(objectCategory=person)(cn=T*))"
     * @param attrs the returning attributes e.g. { "cn" } (can be null)
     * @return the lookup result
     * @throws NamingException exception
     */
    public static List<Map<String, Object>> search(String user, String pass,
        String base, String filter, String[] attrs) throws NamingException {
        String domain = user.substring(user.indexOf('@') + 1);
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://" + domain);
        env.put(Context.SECURITY_PRINCIPAL, user);
        env.put(Context.SECURITY_CREDENTIALS, pass);
        LdapContext context = new InitialLdapContext(env, null);
        SearchControls cons = new SearchControls();
        cons.setSearchScope(SearchControls.SUBTREE_SCOPE);
        if (attrs != null && attrs.length > 0) {
            cons.setReturningAttributes(attrs);
        }
        String name = base;
        if (name == null) {
            name = "dc=" + domain.replaceAll("\\.", ",dc=");
        }
        NamingEnumeration<SearchResult> response
            = context.search(name, filter, cons);
        List<Map<String, Object>> list = new ArrayList<>();
        // Don't use hasMore()
        while (response.hasMoreElements()) {
            Map<String, Object> attributeMap = new HashMap<>();
            NamingEnumeration<? extends Attribute> namingEnumeration
                = response.next().getAttributes().getAll();
            while (namingEnumeration.hasMore()) {
                Attribute a = namingEnumeration.next();
                attributeMap.put(a.getID(), a.get());
            }
            namingEnumeration.close();
            list.add(attributeMap);
        }
        return list;
    }

    /**
     * Checks whether login is successful or not.
     * @param user the user name
     * @param pass the password
     * @return true if login is successful
     */
    public static boolean login(String user, String pass) {
        String[] attr = {CN};
        String name = user.substring(0, user.indexOf('@'));
        String filter = "(&(objectClass=person)(cn=" + name + "))";
        boolean success = false;
        try {
            List<Map<String, Object>> list
                = search(user, pass, null, filter, attr);
            if (list.size() == 1 && name.equals(list.get(0).get(CN))) {
                success = true;
            }
        } catch (AuthenticationException e) {
            success = false;
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
        return success;
    }
}
